using System;
using System.Drawing;
using System.Drawing.Imaging;

namespace ru.yourpictures.Core.Imaging.Filters.PhotoFilters
{
    /// <summary>
    /// Black And White filter class. Turns color images to black and white images.
    /// Supports grayscale, sepia, bright grayscale and custom conversion for public
    /// methods Convert and Apply.
    /// Reference from http://www.bobpowell.net/grayscale.htm and
    /// http://www.codeproject.com/KB/cs/yael_image_filters.aspx
    /// </summary>
    public abstract class BlackAndWhiteBase : FilterBase
    {
        #region ConversionTypes
        /// <summary>Types of conversion</summary>
        public enum ConversionTypes
        {
            /// <summary>Y=0.299RED+0.587GREEN+0.114BLUE</summary>
            Standart,
            /// <summary>Conversion using matrix with 0.5 of each color.</summary>
            Bright,
            /// <summary>Conversion using formula (R+G+B)/3</summary>
            Average,
            /// <summary>
            /// outputRed = (inputRed * .393) + (inputGreen *.769) + (inputBlue * .189)
            /// outputGreen = (inputRed * .349) + (inputGreen *.686) + (inputBlue * .168)
            /// outputBlue = (inputRed * .272) + (inputGreen *.534) + (inputBlue * .131)
            /// </summary>
            Sepia,
            /// <summary>When this type used, custom color matrix has to be provided as parameter</summary>
            Custom
        }
        #endregion

        private ConversionTypes _conversionType = ConversionTypes.Standart;
        private ColorMatrix _colorMatrix = new ColorMatrix(new float[][]{
                                new float[] {0.299f, 0.299f, 0.299f, 0, 0},
                                new float[] {0.587f, 0.587f, 0.587f, 0, 0},
                                new float[] {0.114f, 0.114f, 0.114f, 0, 0},
                                new float[] { 0, 0, 0, 1, 0},
                                new float[] { 0, 0, 0, 0, 0}
                                });

        #region Public properties
        protected ColorMatrix ColorMatrix
        {
            get { return _colorMatrix; }
            set 
            { 
                _colorMatrix = value;
                _conversionType = ConversionTypes.Custom;
            }
        }

        protected ConversionTypes ConversionType
        {
            get { return _conversionType; }
            set 
            {
                if (value != _conversionType)
                {
                    _conversionType = value;
                    if (_conversionType == ConversionTypes.Standart)
                    {
                        _colorMatrix = new ColorMatrix(new float[][]{
                                new float[] {0.299f, 0.299f, 0.299f, 0, 0},
                                new float[] {0.587f, 0.587f, 0.587f, 0, 0},
                                new float[] {0.114f, 0.114f, 0.114f, 0, 0},
                                new float[] { 0, 0, 0, 1, 0},
                                new float[] { 0, 0, 0, 0, 0}
                                });
                    }
                    else if (_conversionType == ConversionTypes.Bright)
                    {
                        _colorMatrix = new ColorMatrix(new float[][]{
                                new float[] {0.5f, 0.5f, 0.5f, 0, 0},
                                new float[] {0.5f, 0.5f, 0.5f, 0, 0},
                                new float[] {0.5f, 0.5f, 0.5f, 0, 0},
                                new float[] { 0, 0, 0, 1, 0},
                                new float[] { 0, 0, 0, 0, 0}
                                });
                    }
                    else if (_conversionType == ConversionTypes.Average)
                    {
                        _colorMatrix = new ColorMatrix(new float[][]{
                                new float[] {0.333f, 0.333f, 0.333f, 0, 0},
                                new float[] {0.333f, 0.333f, 0.333f, 0, 0},
                                new float[] {0.333f, 0.333f, 0.333f, 0, 0},
                                new float[] { 0, 0, 0, 1, 0},
                                new float[] { 0, 0, 0, 0, 0}
                                });
                    }
                    else if (_conversionType == ConversionTypes.Sepia)
                    {
                        _colorMatrix = new ColorMatrix(new float[][]{
                                new float[] {0.393f, 0.769f, 0.189f, 0, 0},
                                new float[] {0.349f, 0.686f, 0.168f, 0, 0},
                                new float[] {0.272f, 0.534f, 0.131f, 0, 0},
                                new float[] { 0, 0, 0, 1, 0},
                                new float[] { 0, 0, 0, 0, 0}
                                });
                    }
                    else if (_conversionType == ConversionTypes.Custom)
                    {
                        // Custom color matrix has to be provided
                    }
                }
            }
        }
        #endregion

        #region Alternative Filter Methods
        /// <summary>
        /// Executes this filter on the input image and returns the result
        /// </summary>
        /// <param name="inputImage">input image</param>
        /// <returns>transformed image</returns>
        public Image AlternativeMethod(Image inputImage, bool bright)
        {
          Bitmap bm = new Bitmap(inputImage.Width, inputImage.Height);
          Graphics g = Graphics.FromImage(bm);
          ColorMatrix cm;

          if (bright)
          {
            cm = new ColorMatrix(new float[][]{   new float[]{0.5f,0.5f,0.5f,0,0},
                                      new float[]{0.5f,0.5f,0.5f,0,0},
                                      new float[]{0.5f,0.5f,0.5f,0,0},
                                      new float[]{0,0,0,1,0,0},
                                      new float[]{0,0,0,0,1,0},
                                      new float[]{0,0,0,0,0,1}});
          }
          else
          {

            //Gilles Khouzams colour corrected grayscale shear
            cm = new ColorMatrix(new float[][]{   new float[]{0.3f,0.3f,0.3f,0,0},
                                    new float[]{0.59f,0.59f,0.59f,0,0},
                                    new float[]{0.11f,0.11f,0.11f,0,0},
                                    new float[]{0,0,0,1,0,0},
                                    new float[]{0,0,0,0,1,0},
                                    new float[]{0,0,0,0,0,1}});
          }

          ImageAttributes ia = new ImageAttributes();
          ia.SetColorMatrix(cm);
          g.DrawImage(inputImage, new Rectangle(0, 0, inputImage.Width, inputImage.Height), 0, 0, inputImage.Width, inputImage.Height, GraphicsUnit.Pixel, ia);
          g.Dispose();
          return bm;
        }
        #endregion Public Filter Methods

        #region Ctor
        public BlackAndWhiteBase() { }
        #endregion

        #region FilterImplementation
        protected override void FilterImplementation()
        {
            ValidatePixelFormat(_b.PixelFormat);


            for (int y = 0; y < _height; y++)
            {
                int index = y * _stride;
                for (int x = 0; x < _width; x++)
                {
                    int gray = 0;

                    if (_conversionType == ConversionTypes.Standart)
                    {
                        gray = (int)(0.299 * _pixels[index + 2] +
                                    0.587 * _pixels[index + 1] +
                                    0.114 * _pixels[index + 0]);
                        if (gray > 255) gray = 255;
                        _pixels[index] = _pixels[index + 1] = _pixels[index + 2] = (byte)gray;
                    }
                    else if (_conversionType == ConversionTypes.Bright)
                    {
                        gray = (int)(0.5 * _pixels[index + 2] +
                                    0.5 * _pixels[index + 1] +
                                    0.5 * _pixels[index + 0]);
                        if (gray > 255) gray = 255;
                        _pixels[index] = _pixels[index + 1] = _pixels[index + 2] = (byte)gray;
                    }
                    else if (_conversionType == ConversionTypes.Sepia)
                    {
                        int i = (int)(0.393 * _pixels[index + 2] +
                                    0.769 * _pixels[index + 1] +
                                    0.189 * _pixels[index + 0]);
                        if (i > 255) i = 255;
                        _pixels[index + 2] = (byte)i;

                        i = (int)(0.349 * _pixels[index + 2] +
                                    0.686 * _pixels[index + 1] +
                                    0.168 * _pixels[index + 0]);
                        if (i > 255) i = 255;
                        _pixels[index + 1] = (byte)i;

                        i = (int)(0.272 * _pixels[index + 2] +
                                    0.534 * _pixels[index + 1] +
                                    0.131 * _pixels[index + 0]);
                        if (i > 255) i = 255;
                        _pixels[index + 0] = (byte)i;
                    }
                    else if (_conversionType == ConversionTypes.Custom)
                    {
                        throw new NotImplementedException("Low level conversion of custom type is not implemented");
                    }

                    index += _bytesPerPixel;
                }
            }
        }
        #endregion

        #region Public Filter Methods
        public override void Apply(Bitmap b)
        {
            _b = b;
            _width = b.Width;
            _height = b.Height;

            // Red Result Green Result Blue Result Alpha Result
            //Red Value .299 .299 .299 0 (Ignored)
            //Green Value .587 .587 .587 0 (Ignored)
            //Blue Value .114 .114 .114 0 (Ignored)
            //Alpha Value 0 0 0 1
            //This is basically saying that:
            // Red should be converted to (R*.299)+(G*.587)+(B*.114)
            // Green should be converted to (R*.299)+(G*.587)+(B*.114)
            // Blue should be converted to (R*.299)+(G*.587)+(B*.114)
            // Alpha should stay the same.
            //Create the colour matrix.
            ImageAttributes attributes = new ImageAttributes();
            attributes.SetColorMatrix(_colorMatrix);

            //Create a new Graphics object from the image.
            Graphics graphics = Graphics.FromImage(b);

            //Draw the image using the ImageAttributes we created.
            graphics.DrawImage(b, new Rectangle(0, 0, b.Width, b.Height), 0, 0, b.Width, b.Height, GraphicsUnit.Pixel, attributes);

            //Dispose of the Graphics object.
            graphics.Dispose();
        }

        public override Bitmap Convert(Bitmap b)
        {
            Bitmap b2 = b.Clone() as Bitmap;
            Apply(b2);
            return b2;
        }
        #endregion
    }
}
